

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "3d_math.h"
#include "3d_canva.h"

//一张画布绑定一个相机
D3Canva::D3Canva(D3Camera *camera)
{
    this->camera = camera;

    width = camera->GetWidth();
    height = camera->GetHeight();

    image = ImageCreate(width, height, IMAGE_FORMAT_BGRA);

    depthGrid = new float *[height];
    for (int32_t i = 0; i < height; i++)
        depthGrid[i] = new float[width];

    flagGrid = new uint8_t *[height];
    for (int32_t i = 0; i < height; i++)
        flagGrid[i] = new uint8_t[width];
    
    Clear();
}

D3Canva::~D3Canva()
{
    if (image.mem.addr)
    {
        delete[] image.mem.addr;
        image.mem.addr = NULL;
    }
    if (depthGrid)
    {
        for (int32_t i = 0; i < height; i++)
            delete[] depthGrid[i];
        delete[] depthGrid;
    }
    if (flagGrid)
    {
        for (int32_t i = 0; i < height; i++)
            delete[] flagGrid[i];
        delete[] flagGrid;
    }
}

int32_t D3Canva::GetWidth() {
    return width;
}
int32_t D3Canva::GetHeight() {
    return height;
}

//获取屏幕缓冲
Image D3Canva::GetImage()
{
    return image;
}

//清空画布
void D3Canva::Clear(uint32_t color)
{
    ImageClear(image, color);
    //清理表格
    for (int32_t i = 0; i < height; i++)
    {
        memset(depthGrid[i], 0, sizeof(float) * width);
        memset(flagGrid[i], 0, sizeof(uint8_t) * width);
        flagCurrent = 0;
    }
}

//渲染模型
void D3Canva::Draw(D3Model *models, int32_t num)
{
    if (!models)
        return;
    for (int32_t i = 0; i < num; i++)
    {
        models[i].partsList->Lock();
        CrossList *node = models[i].partsList->FirstChild();
        while (node)
        {
            DrawPart(*((D3ModelPart*)node->value));
            node = node->Next();
        }
        models[i].partsList->UnLock();
    }
}

//渲染模型部件
void D3Canva::DrawPart(D3ModelPart part)
{
    int32_t retXY[5 * 2] = {0}; //投影到相机屏幕后的二维坐标
    float retDepth[5] = {0}; //投影后坐标的深度信息
    int32_t result[5] = {0}; //投影坐标是否入屏

    //遍历所有三维坐标点
    for (int32_t i = 0, i2 = 0, i3 = 0; i < part.xyzNum; i += 1, i2 += 2, i3 += 3)
    {
        //还原部件实际坐标
        part.xyz[i3] *= part.thickness;
        part.xyz[i3 + 1] *= part.width;
        part.xyz[i3 + 2] *= part.height;
        //转换为相对于相机的坐标
        camera->Transfer(&part.xyz[i3]);
        //投影到相机屏幕
        result[i] = camera->Project(&part.xyz[i3], &retXY[i2], &retDepth[i]);
    }

    if (!camera->IsInside(result, part.xyzNum))
        return;
    flagCurrent += 1;

    //二维渲染
    switch (part.type)
    {
    case D3MODEL_PART_TYPE_LINE:
        D2DrawLine(retXY, retDepth, part.color);
        break;
    case D3MODEL_PART_TYPE_TRIANGLE:
        D2DrawTriangle(retXY, retDepth, part.color);
        break;
    case D3MODEL_PART_TYPE_RECT:
        if (part.film)
            D2DrawRect(retXY, retDepth, part.film);
        else
            D2DrawRect(retXY, retDepth, part.color);
        break;
    default:
        return;
    }
}

//渲染直线
void D3Canva::D2DrawLine(int32_t *xy, float *depth, uint32_t color)
{
    int32_t *retXy = NULL;
    float dp;
    int32_t x, y;
    int32_t ret = line_enum(xy, &retXy);

    if (ret > 0 && retXy)
    {
        //深度分量预处理
        depth[0] /= ret;
        depth[1] /= ret;

        for (int32_t i = 0, i2 = 0; i < ret; i += 1, i2 += 2)
        {
            x = retXy[i2];
            y = retXy[i2 + 1];
            //非已绘制点 && 入屏检查
            if (camera->IsInside(x, y))
            {
                if (flagGrid[y][x] != flagCurrent)
                {
                    //通过当前点到两端点的距离来估算其深度
                    dp = depth[0] * (ret - i) + depth[1] * i;
                    //深度比较
                    if (dp > 0 && (!depthGrid[y][x] || dp < depthGrid[y][x]))
                    {
                        flagGrid[y][x] = flagCurrent;
                        depthGrid[y][x] = dp;
                        ImageWrite(image, x, y, color);
                    }
                }
            }
        }
        free(retXy);
    }
}

//渲染三角形(纯色填充)
void D3Canva::D2DrawTriangle(int32_t *xy, float *depth, uint32_t color)
{
    int32_t i, j, k;
    int32_t maxLine = triangle_max_line(xy);

    int32_t x, y;
    float dp;

    //对于三角形ABC内一点P,有 P = i*A + j*B + k*C, 且 i + j + k = 1
    //即遍历所有的i,j,k数值即可获得所有P点
    for (i = 0; i < maxLine; i += 1)
    {
        for (j = 0; j < maxLine - i; j += 1)
        {
            k = maxLine - i - j;

            x = (i * xy[0] + j * xy[2] + k * xy[4]) / maxLine;
            y = (i * xy[1] + j * xy[3] + k * xy[5]) / maxLine;

            //非已绘制点 && 入屏检查
            if (camera->IsInside(x, y))
            {
                if (flagGrid[y][x] != flagCurrent)
                {
                    //根据三个顶点距离比例评估当前点的深度
                    dp = (i * depth[0] + j * depth[1] + k * depth[2]) / maxLine;
                    //深度比较
                    if (dp > 0 && (!depthGrid[y][x] || dp < depthGrid[y][x]))
                    {
                        flagGrid[y][x] = flagCurrent;
                        depthGrid[y][x] = dp;
                        ImageWrite(image, x, y, color);
                    }
                }
            }
            //非入屏多跳2点,提升性能
            else
                j += 2;
        }
    }
}

//渲染三角形(图片填充)
void D3Canva::D2DrawTriangle(int32_t *xy, float *depth, int32_t *xyToFilm, Film *film)
{
    int32_t i, j, k;
    int32_t maxLine = triangle_max_line(xy);

    int32_t x, y;
    float dp;
    uint32_t color;
    uint32_t *pAddr32 = film->Addr32();

    //对于三角形ABC内一点P,有 P = i*A + j*B + k*C, 且 i + j + k = 1
    //即遍历所有的i,j,k数值即可获得所有P点
    for (i = 0; i < maxLine; i += 1)
    {
        for (j = 0; j < maxLine - i; j += 1)
        {
            k = maxLine - i - j;
            x = (i * xy[0] + j * xy[2] + k * xy[4]) / maxLine;
            y = (i * xy[1] + j * xy[3] + k * xy[5]) / maxLine;

            //非已绘制点 && 入屏检查
            if (camera->IsInside(x, y))
            {
                if (flagGrid[y][x] != flagCurrent)
                {
                    //根据三个顶点距离比例评估当前点的深度
                    dp = (i * depth[0] + j * depth[1] + k * depth[2]) / maxLine;
                    //深度比较,点距离屏幕更近则绘制
                    if (dp > 0 && (!depthGrid[y][x] || dp < depthGrid[y][x]))
                    {
                        color = pAddr32[(i * xyToFilm[1] + j * xyToFilm[3] + k * xyToFilm[5]) / maxLine
                            * film->width + (i * xyToFilm[0] + j * xyToFilm[2] + k * xyToFilm[4]) / maxLine];
                        // 非完全透明点
                        if ((color & 0xFF000000) != 0xFF000000)
                        {
                            flagGrid[y][x] = flagCurrent;
                            depthGrid[y][x] = dp;
                            ImageWrite(image, x, y, color);
                        }
                    }
                }
            }
            //非入屏多跳2点,提升性能
            else
                j += 2;
        }
    }
}

//渲染矩形(纯色填充)
void D3Canva::D2DrawRect(int32_t *xy, float *depth, uint32_t color)
{
    int32_t triangle[6];
    float dp[3];
    //新增一个中心点O
    triangle[4] = (xy[0] + xy[2] + xy[4] + xy[6]) / 4;
    triangle[5] = (xy[1] + xy[3] + xy[5] + xy[7]) / 4;
    dp[2] = (depth[0] + depth[1] + depth[2] + depth[3]) / 4;
    //ABO
    triangle[0] = xy[0]; triangle[1] = xy[1];
    triangle[2] = xy[2]; triangle[3] = xy[3];
    dp[0] = depth[0];
    dp[1] = depth[1];
    D2DrawTriangle(triangle, dp, color);
    //BCO
    triangle[0] = xy[2]; triangle[1] = xy[3];
    triangle[2] = xy[4]; triangle[3] = xy[5];
    dp[0] = depth[1];
    dp[1] = depth[2];
    D2DrawTriangle(triangle, dp, color);
    //CDO
    triangle[0] = xy[4]; triangle[1] = xy[5];
    triangle[2] = xy[6]; triangle[3] = xy[7];
    dp[0] = depth[2];
    dp[1] = depth[3];
    D2DrawTriangle(triangle, dp, color);
    //DAO
    triangle[0] = xy[6]; triangle[1] = xy[7];
    triangle[2] = xy[0]; triangle[3] = xy[1];
    dp[0] = depth[3];
    dp[1] = depth[0];
    D2DrawTriangle(triangle, dp, color);
}

//渲染矩形(图片填充)
void D3Canva::D2DrawRect(int32_t *xy, float *depth, Film *film)
{
    int32_t triangle[6];
    float dp[3];
    int32_t xyToFilm[6];
    //新增一个中心点O
    triangle[4] = (xy[0] + xy[2] + xy[4] + xy[6]) / 4;
    triangle[5] = (xy[1] + xy[3] + xy[5] + xy[7]) / 4;
    dp[2] = (depth[0] + depth[1] + depth[2] + depth[3]) / 4;
    xyToFilm[4] = film->width / 2;
    xyToFilm[5] = film->height / 2;
    //ABO
    triangle[0] = xy[0]; triangle[1] = xy[1];
    triangle[2] = xy[2]; triangle[3] = xy[3];
    dp[0] = depth[0];
    dp[1] = depth[1];
    xyToFilm[0] = 0;
    xyToFilm[1] = 0;
    xyToFilm[2] = film->width - 1;
    xyToFilm[3] = 0;
    D2DrawTriangle(triangle, dp, xyToFilm, film);
    //BCO
    triangle[0] = xy[2]; triangle[1] = xy[3];
    triangle[2] = xy[4]; triangle[3] = xy[5];
    dp[0] = depth[1];
    dp[1] = depth[2];
    xyToFilm[0] = film->width - 1;
    xyToFilm[1] = 0;
    xyToFilm[2] = film->width - 1;
    xyToFilm[3] = film->height - 1;
    D2DrawTriangle(triangle, dp, xyToFilm, film);
    //CDO
    triangle[0] = xy[4]; triangle[1] = xy[5];
    triangle[2] = xy[6]; triangle[3] = xy[7];
    dp[0] = depth[2];
    dp[1] = depth[3];
    xyToFilm[0] = film->width - 1;
    xyToFilm[1] = film->height - 1;
    xyToFilm[2] = 0;
    xyToFilm[3] = film->height - 1;
    D2DrawTriangle(triangle, dp, xyToFilm, film);
    //DAO
    triangle[0] = xy[6]; triangle[1] = xy[7];
    triangle[2] = xy[0]; triangle[3] = xy[1];
    dp[0] = depth[3];
    dp[1] = depth[0];
    xyToFilm[0] = 0;
    xyToFilm[1] = film->height - 1;
    xyToFilm[2] = 0;
    xyToFilm[3] = 0;
    D2DrawTriangle(triangle, dp, xyToFilm, film);
}
